package com.joinway.framework.data.jdbc.repository.utils

import java.lang.reflect.Field
import java.util.concurrent.ConcurrentHashMap

import javax.persistence.Id

import org.apache.commons.lang3.StringUtils
import org.slf4j.Logger
import org.slf4j.LoggerFactory

import com.joinway.framework.bean.domain.annonation.DateCreated
import com.joinway.framework.bean.domain.annonation.LastUpdated
import com.joinway.framework.bean.domain.constant.SqlConstants
import com.joinway.framework.data.jdbc.bean.DomainClassMetaData
import com.joinway.framework.data.jdbc.bean.DomainFieldMetaData
import com.joinway.framework.data.jdbc.exception.JdbcDataException
import com.joinway.framework.data.jdbc.type.ColumnCase
import com.joinway.framework.extension.groovy.DomainUtils
import com.joinway.framework.extension.utils.DataUtils

final class JdbcDataFactory {

	private final static Logger log = LoggerFactory.getLogger(JdbcDataFactory.class)
	
	/**
	 * key - domain class, value - domain class meta data
	 */
	private static final ConcurrentHashMap<Class, DomainClassMetaData> domainClassCofnig = new ConcurrentHashMap()

	private static final String AND_KEY = "And"
	
	static DomainClassMetaData createDomainClassMetaData(Class domainClass){
		parse(domainClass)
		domainClassCofnig.get(domainClass)
	}
	
	/**
	 * 
	 * @param conditionText	the suffix of "findBy"
	 * @param table	table domain class or table name
	 * @param columnCase upper case, lower case or keep as it is
	 * @return
	 */
	static String createSelectSql(String conditionText, def table, ColumnCase columnCase){
		String tableName
		
		if(table instanceof Class){
			parse(table)
			DomainClassMetaData domainClassMetaData = domainClassCofnig.get(table)
			tableName = domainClassMetaData.tableName
		}else{
			tableName = table
		}
		
		String conditions = ""
		
		conditionText.split(AND_KEY).each {
			String fieldName = StringUtils.uncapitalize(it)
			
			String columnName = DomainUtils.convertToDBName(fieldName)
			if(ColumnCase.Lower == columnCase){
				columnName = columnName.toLowerCase()
			}else if(ColumnCase.Upper == columnCase){
				columnName = columnName.toUpperCase()
			}
			
			conditions += "and $columnName = ? "
		}

		"select * from $tableName where 1 = 1 $conditions"
	} 

	private static void parse(Class domainClass) throws Exception {
		if(domainClass && !domainClassCofnig.containsKey(domainClass)){
			String tableName = DomainUtils.getTableName(domainClass)
			
			List<DomainFieldMetaData> fieldMetas = []
			
			DomainClassMetaData domainMeta = new DomainClassMetaData()
			domainMeta.tableName = tableName;
			domainMeta.domainClass = domainClass
			domainMeta.domainFieldMetaDatas = fieldMetas
			
			Set<Field> fields = DataUtils.getValidFields(domainClass)
			
			boolean foundIdKey = false
			DomainFieldMetaData idKeyCandidate
			
			fields.each {
				Field field = it
				
				DomainFieldMetaData meta = new DomainFieldMetaData()
				meta.field = field
				meta.columnName = DomainUtils.getColumnName(field)
				
				DateCreated dateCreated = field.getAnnotation(DateCreated.class)
				if(dateCreated){
					if(!isValidDateType(field.getType())) throw new JdbcDataException(DateCreated.class.getName() + " must annotate on Date type")
					meta.dateCreated = true
				}
				
				LastUpdated lastUpdated = field.getAnnotation(LastUpdated.class)
				if(lastUpdated){
					meta.lastUpdated = true
				}
				
				Id id = field.getAnnotation(Id.class)
				if(id){
					meta.idKey = true
					foundIdKey = true
				}
				
				if(SqlConstants.Key.Id.equals(field.getName())){
					idKeyCandidate = meta
				}
				
				fieldMetas.add(meta)
			}
			
			if(!foundIdKey){
				if(idKeyCandidate){
					idKeyCandidate.idKey = true
				}else{
					log.warn("{} no primary key was found!", domainClass.getName())
				}
			}
			
			domainClassCofnig.putIfAbsent(domainClass, domainMeta)
		}
	}

	private static boolean isValidDateType(Class target){
		target == Date || target == java.sql.Date || target == java.sql.Timestamp
	}
	
	private JdbcDataFactory(){}
}
